using System;
using System.Collections.Generic;
using System.Linq;
using Foundation.ExtensionMethods;
using Microsoft.Win32;

namespace Foundation.FileAssotiations
{
    /// <summary>
    /// Provides instance of ProgramaticID that can be referenced by multiple different extensions.
    /// </summary>
    public class ProgramInfo
    {
        private const string AlwaysShowExtensionSubKeyName = "AlwaysShowExt";
        private const string DefaultIconValueName = "DefaultIcon";
        private const string EditFlagsValueName = "EditFlags";
        private const string ShellSubKeyName = "shell";

        /// <summary>
        /// Gets or sets a value that determines if the file's extension will always be displayed.
        /// </summary>
        public bool AlwaysShowExtension { get; set; }

        /// <summary>
        /// Gets or sets a value that determines what the friendly name of the file is.
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Gets or sets a value that determines numerous shell options for extension as well as limitations on how extension properties can be edited by programs that honor <see cref="EditFlags"/>
        /// </summary>
        public EditFlags EditFlags { get; set; }

        /// <summary>
        /// Gets or sets a value that determines the default icon for the file type.
        /// </summary>
        public ProgramIcon DefaultIcon { get; set; }

        /// <summary>
        /// Gets or sets an array of <see cref="ProgramVerb"/> that define the verbs supported by this ProgID
        /// </summary>
        public List<ProgramVerb> Verbs { get; set; }

        /// <summary>
        /// Gets a value that is the name of the Programatic Identifier
        /// </summary>
        public string ProgramId { get; private set; }

        /// <summary>
        /// Deletes the specified program id.
        /// </summary>
        /// <param name="programId">The program id.</param>
        public static void Delete(string programId)
        {
            if (Registry.ClassesRoot.Exists(programId))
                Registry.ClassesRoot.DeleteSubKeyTree(programId);
        }

        /// <summary>
        /// Gets the specified program id.
        /// </summary>
        /// <param name="programId">The program id.</param>
        /// <returns></returns>
        public static ProgramInfo Get(string programId)
        {
            if (!Registry.ClassesRoot.Exists(programId))
                return new ProgramInfo {ProgramId = programId, EditFlags = EditFlags.None};

            var info = new ProgramInfo
                           {
                               ProgramId = programId,
                               AlwaysShowExtension = Registry.ClassesRoot.Exists(string.Format(@"{0}\{1}", programId, AlwaysShowExtensionSubKeyName)),
                               Description = Registry.ClassesRoot.GetValueByPath(programId, string.Empty) as string, DefaultIcon = ProgramIcon.Parse(Registry.ClassesRoot.GetValueByPath(programId, "DefaultIcon") as string),
                           };

            var editFlagsValue = Registry.ClassesRoot.GetValueByPath(programId, EditFlagsValueName) as string;
            if (editFlagsValue != null)
            {
                EditFlags flags;
                if (Enum.TryParse(editFlagsValue, out flags))
                    info.EditFlags = flags;
            }

            using (var key = Registry.ClassesRoot.CreateOrOpenSubKey(programId))
            {
                using (var shellSubKey = key.CreateOrOpenSubKey("shell"))
                {
                    var verbSubKeys = shellSubKey.GetSubKeyNames();
                    info.Verbs = (from verbSubKey in verbSubKeys
                                  let command = shellSubKey.GetValue(verbSubKey) as string
                                  select new ProgramVerb(verbSubKey, command)).ToList();
                }
            }

            return info;
        }

        /// <summary>
        /// Sets the specified program info.
        /// </summary>
        /// <param name="info">The program info.</param>
        public static void Set(ProgramInfo info)
        {
            using (var key = Registry.ClassesRoot.CreateOrOpenSubKey(info.ProgramId))
            {
                using (var shellKey = key.OpenSubKey(ShellSubKeyName))
                {
                    if (shellKey != null)
                        key.DeleteSubKeyTree(ShellSubKeyName);
                }

                using (var shellSubKey = key.CreateOrOpenSubKey("shell"))
                {
                    foreach (var verb in info.Verbs)
                    {
                        shellSubKey.SetValueByPath(verb.Name + @"\command", string.Empty, verb.Command);
                    }
                }

                if (info.AlwaysShowExtension)
                    key.CreateOrOpenSubKey(AlwaysShowExtensionSubKeyName);
                else if (key.Exists(AlwaysShowExtensionSubKeyName))
                    key.DeleteSubKeyTree(AlwaysShowExtensionSubKeyName);

                key.SetValue(string.Empty, info.Description);
            }

            Registry.ClassesRoot.SetValueByPath(info.ProgramId, DefaultIconValueName, info.DefaultIcon.ToString());
            Registry.ClassesRoot.SetValueByPath(info.ProgramId, EditFlagsValueName, info.EditFlags);
            ShellNotification.NotifyOfChange();
        }
    }

    /// <summary>
    /// Specifies values that control some aspects of the Shell's handling of the file types linked to a ProgID.
    /// </summary>
    [Flags]
    public enum EditFlags : uint
    {
        /// <summary>
        /// No flags exist
        /// </summary>
        None = 0x0000000,

        /// <summary>
        /// Exclude the file class.
        /// </summary>
        Exclude = 0x00000001,

        /// <summary>
        /// Show file classes, such as folders, that aren't associated with a file name extension.
        /// </summary>
        Show = 0x00000002,

        /// <summary>
        /// The file class has a file name extension.
        /// </summary>
        HasExtension = 0x00000004,

        /// <summary>
        /// The registry entries associated with this file class cannot be edited. New entries cannot be added and existing entries cannot be modified or deleted.
        /// </summary>
        NoEdit = 0x00000008,

        /// <summary>
        /// The registry entries associated with this file class cannot be deleted.
        /// </summary>
        NoRemove = 0x00000010,

        /// <summary>
        /// No new verbs can be added to the file class.
        /// </summary>
        NoNewVerb = 0x00000020,

        /// <summary>
        /// Canonical verbs such as open and print cannot be modified or deleted.
        /// </summary>
        NoEditVerb = 0x00000040,

        /// <summary>
        /// Canonical verbs such as open and print cannot be deleted.
        /// </summary>
        NoRemoveVerb = 0x00000080,

        /// <summary>
        /// The description of the file class cannot be modified or deleted.
        /// </summary>
        NoEditDesc = 0x00000100,

        /// <summary>
        /// The icon assigned to the file class cannot be modified or deleted.
        /// </summary>
        NoEditIcon = 0x00000200,

        /// <summary>
        /// The default verb cannot be modified.
        /// </summary>
        NoEditDflt = 0x00000400,

        /// <summary>
        /// The commands associated with verbs cannot be modified.
        /// </summary>
        NoEditVerbCmd = 0x00000800,

        /// <summary>
        /// Verbs cannot be modified or deleted.
        /// </summary>
        NoEditVerbExe = 0x00001000,

        /// <summary>
        /// The Dynamic Data Exchange (DDE)-related entries cannot be modified or deleted.
        /// </summary>
        NoDDE = 0x00002000,

        /// <summary>
        /// The content-type and default-extension entries cannot be modified or deleted.
        /// </summary>
        NoEditMIME = 0x00008000,

        /// <summary>
        /// The file class's open verb can be safely invoked for downloaded files. Note that this flag may create a security risk, because downloaded files could contain malicious content. To reduce this risk, consider methods to scan downloaded files before opening.
        /// </summary>
        OpenIsSafe = 0x00010000,

        /// <summary>
        /// Do not allow the Never ask me check box to be enabled. The user can override this attribute through the File Type dialog box. This flag also affects ShellExecute, download dialogs, and any application making use of the AssocIsDangerous function.
        /// </summary>
        AlwaysUnsafe = 0x00020000,

        /// <summary>
        /// Always show the file class's file name extension, even if the user has selected the Hide Extensions option.
        /// </summary>
        AlwaysShowExtension = 0x00040000,

        /// <summary>
        /// Don't add members of this file class to the Recent Documents folder.
        /// </summary>
        NoRecentDocuments = 0x00100000
    }
}